#include <stdio.h>
#include <stdio.h>
#include <unistd.h>
#include <errno.h>
#include <string.h>
#include <assert.h>
#include <arpa/inet.h>
#include <sys/types.h>
#include <sys/socket.h>

#include <mutex>
#include <atomic>
#include <thread>

#include "co_api.h"
#include "common/common_utils.h"

using namespace std;

#define SERVER_HOST "127.0.0.1"
#define SERVER_PORT 6666

#define MAX_BUFF_SIZE 1024

#define LOG_FILE "a.log"

#define CREATE_TASK CoApi::create

#define PRINTF(fmt, ...) \
{ \
    printf("[%s] " fmt, CommonUtils::date_ms().c_str(), ##__VA_ARGS__); \
}

//int connect_server(const string& host, int port);

int echo_client_run(int loop);
int echo_server_run();

int flow_test();
int multi_client_test();

int main()
{
    LogDefaulter::set_log_level(LOG_LEVEL_DEBUG);
    LogDefaulter::set_logger([](const string& message) {
        printf("socket_test|%s\n", message.c_str());         
    });    

    PRINTF("pid:%d\n", getpid());
   // flow_test();
    multi_client_test();
    PRINTF("goto end main ...\n");
    PRINTF("\n");
    PRINTF("\n");
    PRINTF("\n");
    return 0;
}

int flow_test()
{
    PRINTF("test_flow start\n");

    bool set_end = false;

    PRINTF("ready to create task server\n");
    CREATE_TASK([] {
        echo_server_run();        
    });

    usleep(100 * 1000);

    PRINTF("ready to create task client\n");
    CREATE_TASK([&set_end] {
        echo_client_run(10); 
        set_end = true;
    });

    while (!set_end) {
        usleep(100 * 1000); 
    }

    PRINTF("test_flow finish\n");

    return 0;
}

int multi_client_test()
{
    const int CLIENT_NUM = 10;

    PRINTF("multi_client_flow start\n");

    atomic<int> counter(0);

    PRINTF("ready to create task server\n");
    CREATE_TASK([] {
        echo_server_run();
    });

    usleep(100 * 1000);

    vector<thread> threads;

    PRINTF("ready to create task client\n");
    for (int i = 0; i < CLIENT_NUM; i++) {
        CREATE_TASK([&counter] {
            PRINTF(
                "########## tid:%d, cid:%d, new client coroutine comming ...\n", 
                CommonUtils::gettid(),
                CoApi::getcid()
            );
            echo_client_run(10);
            counter++;
        });
    }
    PRINTF("create task client finish, count:%d\n", CLIENT_NUM);

    while (counter.load() != CLIENT_NUM) {
        usleep(100 * 1000); 
    }

    PRINTF("multi_client_flow finish\n");

    return 0;
}

int echo_client_run(int loop)
{
    PRINTF(
        "client|tid:%d, cid:%d, start echo_client\n", 
        CommonUtils::gettid(), 
        CoApi::getcid()
    );

    int s = socket(AF_INET, SOCK_STREAM, 0);
    assert(s >= 0);

    struct sockaddr_in caddr;
    caddr.sin_family = AF_INET;
    caddr.sin_port   = htons(SERVER_PORT);
    caddr.sin_addr.s_addr = inet_addr(SERVER_HOST);

    PRINTF(
        "client|tid:%d, cid:%d, connect ready, fd:%d, host:%s, port:%d\n", 
        CommonUtils::gettid(), 
        CoApi::getcid(), 
        s, 
        SERVER_HOST, 
        SERVER_PORT
    );
    int ret = connect(s, (struct sockaddr*)&caddr, sizeof(caddr));
    if (ret == -1) {
        PRINTF(
            "client|tid:%d, cid:%d, onnect fail, host:%s, port:%d\n", 
            CommonUtils::gettid(),
            CoApi::getcid(),
            SERVER_HOST, 
            SERVER_PORT
        );
        return -1;
    }
    PRINTF(
        "client|tid:%d, cid:%d, connect succeed, fd:%d, host:%s, port:%d\n", 
         CommonUtils::gettid(),
         CoApi::getcid(), 
         s, 
         SERVER_HOST, 
         SERVER_PORT
    );

    int i = 0;
    while (loop < 0 || i < loop) {

        char buffer[MAX_BUFF_SIZE] = {0};
        snprintf(buffer, sizeof(buffer), "hello %d", i);

        int ret = send(s, buffer, strlen(buffer), 0); 
        if (ret == -1) {
            PRINTF(
                "client|tid:%d, cid:%d, send fail, fd:%d, buffer:%s, errno:%d\n", 
                CommonUtils::gettid(),
                CoApi::getcid(),
                s,
                buffer, 
                errno
            );
            return -1; 
        }

        buffer[0] = 0;
        ret = recv(s, buffer, sizeof(buffer), 0);
        if (ret == -1) {
            PRINTF(
                "client|tid:%d, cid:%d, recv fail, fd:%d, errno:%d\n", 
                CommonUtils::gettid(),
                CoApi::getcid(),
                s,
                errno
            ); 
            return -1;
        }

        PRINTF(
            "client|tid:%d, cid:%d, fd:%d, recv buffer:%s\n", 
            CommonUtils::gettid(),
            CoApi::getcid(),
            s,
            buffer
        );
        i++;
    }
    PRINTF(
        "client|tid:%d, cid:%d, fd:%d, finsh\n", 
        CommonUtils::gettid(), 
        CoApi::getcid(),
        s
    );
    close(s);
    return 0;
}

int echo_server_run()
{
    PRINTF(
        "ssssssssss|tid:%d, cid:%d, start echo_server\n", 
        CommonUtils::gettid(), 
        CoApi::getcid()
    );

    int s = socket(AF_INET, SOCK_STREAM, 0);
    assert(s >= 0);

    int ret;

    int v = 1;
    ret = setsockopt(s, SOL_SOCKET, SO_REUSEADDR, (void*)&v, sizeof(v));
    if (ret != 0) {
        PRINTF(
            "ssssssssss|tid:%d, cid:%d, set reuse address fail, errno:%d, error:%s\n",
            CommonUtils::gettid(),
            CoApi::getcid(),
            errno,
            strerror(errno)
        );
        return -1; 
    }

    struct sockaddr_in saddr;
    saddr.sin_family = AF_INET;
    saddr.sin_port   = htons(SERVER_PORT);
    saddr.sin_addr.s_addr = htonl(INADDR_ANY);

    ret = bind(s, (struct sockaddr*)&saddr, sizeof(saddr));
    if (ret == -1) {
        PRINTF(
            "ssssssssss|tid:%d, cid:%d, bind fail, errno:%d, error:%s\n", 
            CommonUtils::gettid(), 
            CoApi::getcid(), 
            errno,
            strerror(errno)
        );
        return -1; 
    }
    PRINTF(
        "ssssssssss|tid:%d, cid:%d, bind succeed, port:%d\n", 
        CommonUtils::gettid(), 
        CoApi::getcid(), 
        SERVER_PORT
    );

   // ret = listen(s, 5);
    ret = listen(s, 100);
    if (ret == -1) {
        PRINTF(
            "ssssssssss|tid:%d, cid:%d, listen fail, fd:%d, errno:%d, error:%s\n", 
            CommonUtils::gettid(), 
            CoApi::getcid(), 
            s,
            errno,
            strerror(errno)
        );
        return -1; 
    }
    PRINTF(
        "ssssssssss|tid:%d, cid:%d, fd:%d, listen succeed\n", 
        CommonUtils::gettid(), 
        CoApi::getcid(),
        s
    );

    while (1) {

        struct sockaddr_in caddr;
        socklen_t len = sizeof(caddr);

        std::default_random_engine e;
        std::uniform_int_distribution<int> u(0, 1000000);
        e.seed(time(0));

        int seq = u(e);
        PRINTF(
            "ssssssssss|tid:%d, cid:%d, accept ready, seq:%d\n", 
            CommonUtils::gettid(), 
            CoApi::getcid(), 
            seq
        );
        int c = accept(s, (struct sockaddr*)&caddr, &len);
        if (c < 0) {
            PRINTF(
                "ssssssssss|tid:%d, cid:%d, accept fail, seq:%d, sfd:%d, errno:%d, error:%s\n", 
                CommonUtils::gettid(),
                CoApi::getcid(),
                seq,
                s,
                errno,
                strerror(errno)
            );
        } else {
            PRINTF(
                "ssssssssss|tid:%d, cid:%d, accept pass, new connect, seq:%d, fd:%d\n", 
                CommonUtils::gettid(),
                CoApi::getcid(),
                seq,
                c
            );
        }

        assert(c >= 0);

        CREATE_TASK([c] {

            while (1) {

                char buffer[MAX_BUFF_SIZE] = {0};

                int ret = recv(c, buffer, sizeof(buffer), 0); 
                if (ret == -1) {
                    PRINTF(
                        "ssssssssss|tid:%d, cid:%d, recv fail, fd:%d, errno:%d, error:%s\n", 
                        CommonUtils::gettid(), 
                        CoApi::getcid(), 
                        c, 
                        errno,
                        strerror(errno)
                    );
                    break ;
                }
                if (ret == 0) {
                    PRINTF(
                        "server|tid:%d, cid:%d, peer close, fd:%d\n", 
                        CommonUtils::gettid(), 
                        CoApi::getcid(), 
                        c
                    );
                    break ; 
                }
                buffer[ret] = 0;
                PRINTF(
                    "ssssssssss|tid:%d, cid:%d, recv data, fd:%d, buffer:%s\n", 
                    CommonUtils::gettid(), 
                    CoApi::getcid(), 
                    c, 
                    buffer
                );

                ret = send(c, buffer, strlen(buffer), 0);
                if (ret == -1) {
                    PRINTF(
                        "server|tid:%d, cid:%d, send fail, fd:%d, errno:%d, error:%s\n", 
                        CommonUtils::gettid(), 
                        CoApi::getcid(), 
                        c, 
                        errno,
                        strerror(errno)
                    );
                    break ;
                }
                PRINTF(
                    "ssssssssss|tid:%d, cid:%d, send data, fd:%d, buffer:%s\n", 
                    CommonUtils::gettid(), 
                    CoApi::getcid(), 
                    c, 
                    buffer
                );
            } 
            PRINTF(
                "ssssssssss|tid:%d, cid:%d, client finish, fd:%d\n", 
                CommonUtils::gettid(), 
                CoApi::getcid(), 
                c
            );
            close(c);
        });
        PRINTF(
            "ssssssssss|tid:%d, cid:%d, accept after create client coroutine, seq:%d\n", 
            CommonUtils::gettid(), 
            CoApi::getcid(), 
            seq
        );
    }
    PRINTF(
        "ssssssssss|tid:%d, cid:%d, server_fd:%d, echo_server finish\n", 
        CommonUtils::gettid(), 
        CoApi::getcid(),
        s
    );
    close(s);
    return 0;
}

